package lex

import (
	"fmt"
	"unicode"

	"gitcode.com/deyiyangyang/showen-lang/tok"
)

// Tokenizer is a lexer for Scheme code
type Tokenizer struct {
	input string
	pos   int
	line  int
	col   int
}

// NewTokenizer creates a new lexer
func NewTokenizer(input string) *Tokenizer {
	return &Tokenizer{
		input: input,
		pos:   0,
		line:  1,
		col:   1,
	}
}

// NextToken returns the next token
func (t *Tokenizer) NextToken() (*tok.Token, error) {
	// Skip whitespace characters
	t.skipWhitespace()

	// Check if end of input is reached
	if t.pos >= len(t.input) {
		return &tok.Token{Type: tok.TOKEN_EOF}, nil
	}

	char := t.input[t.pos]

	// Handle parentheses
	if char == '(' {
		t.pos++
		t.col++
		return &tok.Token{Type: tok.TOKEN_LPAREN, Value: "("}, nil
	}
	if char == ')' {
		t.pos++
		t.col++
		return &tok.Token{Type: tok.TOKEN_RPAREN, Value: ")"}, nil
	}

	// Handle quotes
	if char == '\'' || char == '`' {
		t.pos++
		t.col++
		return &tok.Token{Type: tok.TOKEN_QUOTE, Value: string(char)}, nil
	}

	// Handle dot
	if char == '.' {
		t.pos++
		t.col++
		return &tok.Token{Type: tok.TOKEN_DOT, Value: "."}, nil
	}

	// Handle strings
	if char == '"' {
		return t.readString()
	}

	// Handle numbers
	if unicode.IsDigit(rune(char)) || char == '-' {
		return t.readNumber()
	}

	// Handle booleans and symbols
	if unicode.IsLetter(rune(char)) || char == '+' || char == '*' || char == '/' || char == '>' || char == '<' || char == '=' || char == '?' || char == '!' {
		return t.readSymbol()
	}

	return nil, fmt.Errorf("unexpected character at line %d, column %d: %c", t.line, t.col, char)
}

// skipWhitespace skips whitespace characters and comments
func (t *Tokenizer) skipWhitespace() {
	for t.pos < len(t.input) {
		char := t.input[t.pos]
		if unicode.IsSpace(rune(char)) {
			// Handle newline
			if char == '\n' {
				t.line++
				t.col = 1
			} else {
				t.col++
			}
			t.pos++
		} else if char == ';' {
			// Skip comments until end of line or file
			for t.pos < len(t.input) && t.input[t.pos] != '\n' {
				t.pos++
				t.col++
			}
		} else {
			break
		}
	}
}

// readString reads string literals
func (t *Tokenizer) readString() (*tok.Token, error) {
	t.pos++
	t.col++
	start := t.pos

	for t.pos < len(t.input) {
		char := t.input[t.pos]
		if char == '\\' {
			// Handle escape characters
			t.pos++
			t.col++
		} else if char == '"' {
			value := t.input[start:t.pos]
			t.pos++
			t.col++
			return &tok.Token{Type: tok.TOKEN_STRING, Value: value}, nil
		} else if char == '\n' {
			// Newlines are not allowed in strings
			return nil, fmt.Errorf("unclosed string at line %d, column %d", t.line, t.col)
		}
		t.pos++
		t.col++
	}

	return nil, fmt.Errorf("unclosed string at EOF")
}

// readNumber reads number literals
func (t *Tokenizer) readNumber() (*tok.Token, error) {
	start := t.pos
	hasDecimal := false
	hasSlash := false

	// Allow minus sign as the first character, but check if there's a digit after
	if t.pos < len(t.input) && t.input[t.pos] == '-' {
		// Check if there's a digit after the minus sign
		if t.pos+1 < len(t.input) && (unicode.IsDigit(rune(t.input[t.pos+1])) || t.input[t.pos+1] == '/') {
			t.pos++
			t.col++
		} else {
			// If there's no digit after the minus sign, this is a symbol
			t.pos++
			t.col++
			value := t.input[start:t.pos]
			return &tok.Token{Type: tok.TOKEN_SYMBOL, Value: value}, nil
		}
	}

	// Read the number part
	for t.pos < len(t.input) {
		char := t.input[t.pos]
		if unicode.IsDigit(rune(char)) {
			t.pos++
			t.col++
		} else if char == '.' && !hasDecimal && !hasSlash {
			hasDecimal = true
			t.pos++
			t.col++
		} else if char == '/' && !hasSlash && !hasDecimal {
			hasSlash = true
			t.pos++
			t.col++
			// Check if there's a digit after the slash
			if t.pos >= len(t.input) || !unicode.IsDigit(rune(t.input[t.pos])) {
				return nil, fmt.Errorf("invalid number format at line %d, column %d", t.line, t.col)
			}
		} else {
			break
		}
	}

	value := t.input[start:t.pos]
	return &tok.Token{Type: tok.TOKEN_NUMBER, Value: value}, nil
}

// readSymbol reads symbols and special identifiers
func (t *Tokenizer) readSymbol() (*tok.Token, error) {
	start := t.pos

	for t.pos < len(t.input) {
		char := t.input[t.pos]
		if unicode.IsLetter(rune(char)) || unicode.IsDigit(rune(char)) || char == '+' || char == '-' || char == '*' || char == '/' || char == '>' || char == '<' || char == '=' || char == '?' || char == '!' || char == '_' || char == '$' || char == '%' || char == '&' || char == '|' || char == '{' || char == '}' || char == '[' || char == ']' {
			t.pos++
			t.col++
		} else {
			break
		}
	}

	value := t.input[start:t.pos]

	// Check if it's a boolean value
	if value == "#t" {
		return &tok.Token{Type: tok.TOKEN_TRUE, Value: value}, nil
	} else if value == "#f" {
		return &tok.Token{Type: tok.TOKEN_FALSE, Value: value}, nil
	}

	return &tok.Token{Type: tok.TOKEN_SYMBOL, Value: value}, nil
}
